﻿module TerrainFunctions

open Measure
open DataTypes
open Utilities
open GameState



//         TERRAIN MESH
//  (0,0)---->
//    | *---*---*---*---*
//    v | \ | / | \ | / |         ^
//      *---*---*---*---*         |
//      | / | \ | / | \ |         |
//      *---*---*---*---*         |
//      | \ | / | \ | / |         -
//      *---*---*---*---*      Terrain
//      | / | \ | / | \ |     scrolling
//      *---*---*---*---*
//            |    \________________
//            |                     |
//            V                     V
//
//       CELL TYPE 1            CELL TYPE 2
//       v0*----*v1             v0*----*v1        rowFactor
//         | \  |                 |  / |             ^
//         |  \ |                 | /  |             |
//       v2*----*v3             v2*----*v3           +----> colFactor
//            
//            |                      |
//            v                      v
//
//          v1   v0               v0   v1
//      v1   *---*                *---*   v1
//        *   \  | upper    upper |  /   *
//        | \  \ |                | /  / |
//  lower |  \   *v2            v2*   /  | lower
//        *---*                      *---*
//        v0   v2                   v2   v0



let private GetWrappedLeftVertex (terrainData:float32<m>[,]) row col =
    if col > 0 then
        terrainData.[row,col-1]
    else terrainData.[row,col]



let private GetWrappedRightVertex (terrainData:float32<m>[,]) row col =
    if col < terrainData.GetLength(1)-1 then
        terrainData.[row,col+1]
    else terrainData.[row,col]



let private GetWrappedUpVertex (terrainData:float32<m>[,]) row col =
    if row > 0 then
        terrainData.[row-1,col]
    else terrainData.[row,col]



let private GetWrappedDownVertex (terrainData:float32<m>[,]) row col =
    if row < terrainData.GetLength(0)-1 then
        terrainData.[row+1,col]
    else terrainData.[row,col]



let private ComputeNormal (terrainData:float32<m>[,]) row col =
    let left = GetWrappedLeftVertex terrainData row col
    let up = GetWrappedUpVertex terrainData row col
    let down = GetWrappedDownVertex terrainData row col
    let right = GetWrappedRightVertex terrainData row col
    Vector3<1>.Normalize (Vector3<1>((left - right) |> float32, 2.0f, (up - down) |> float32))



let internal InitializeTerrainVertexData (terrainData:float32<m>[,]) (cellSpacing:float32<m>) = 
    [|
        for i = 0 to terrainData.GetLength(0)-1 do
            yield
                [|
                    for j = 0 to terrainData.GetLength(1)-1 do
                        let currentZ = -cellSpacing * ((terrainData.GetLength(0) - 1 - i) |> float32)
                        yield
                            {
                                position = Vector3<m>(cellSpacing*(j |> float32), terrainData.[i,j], currentZ)
                                normal = ComputeNormal terrainData i j
                            }
                |]
    |]



let internal InitializeTerrainCellData (vertexdata : array<array<TerrainVertex>> ref) = 
    assert(!vertexdata <> [||])
    [|
        for i = 0 to vertexdata.Value.Length-2 do
            yield
                [|
                    for j = 0 to vertexdata.Value.[0].Length-2 do

                        let v0 = ref vertexdata.Value.[i].[j]
                        let v1 = ref vertexdata.Value.[i].[j+1]
                        let v2 = ref vertexdata.Value.[i+1].[j]
                        let v3 = ref vertexdata.Value.[i+1].[j+1]

                        if (i%2 = 0) && (j%2 = 0) || (i%2 <> 0) && (j%2 <> 0) then
                        // CELL TYPE 1
                            yield
                                {
                                    upperTriangle =
                                        {
                                            v0 = v1
                                            v1 = v0
                                            v2 = v3
                                            normal = Vector3.Lerp(0.5f, Vector3.Lerp(0.5f, (!v0).normal, (!v1).normal), (!v3).normal)
                                        }
                                    lowerTriangle =
                                        {
                                            v0 = v2
                                            v1 = v0
                                            v2 = v3
                                            normal = Vector3.Lerp(0.5f, Vector3.Lerp(0.5f, (!v0).normal, (!v2).normal), (!v3).normal)
                                        }
                                    type1 = true
                                }
                        else
                        // CELL TYPE 2
                            yield
                                {
                                    upperTriangle =
                                        {
                                            v0 = v0
                                            v1 = v1
                                            v2 = v2
                                            normal = Vector3.Lerp(0.5f, Vector3.Lerp(0.5f, (!v0).normal, (!v1).normal), (!v2).normal)
                                        }
                                    lowerTriangle =
                                        {
                                            v0 = v3
                                            v1 = v1
                                            v2 = v2
                                            normal = Vector3.Lerp(0.5f, Vector3.Lerp(0.5f, (!v1).normal, (!v2).normal), (!v3).normal)
                                        }
                                    type1 = false
                                }
                |]
    |]



let internal InitializeTerrainSpawnTriggers (difficult:int) (startZ:float32<m>) (endZ:float32<m>) =
    [
        let spawnNums = 
            match difficult with
            | 0 -> 55 + ((Rand.NextFloat * 10.0f) |> int)
            | 1 | _ -> 65 + ((Rand.NextFloat * 15.0f) |> int)

        let firstSpawnZ = -110.0f<m>
        let spawnStep = (startZ + firstSpawnZ - endZ) / (spawnNums |> float32)

        for i = 0 to spawnNums do
            yield (i |> float32) * -spawnStep + firstSpawnZ
    ]



let private RetrieveTerrainCellRow (terrain:Terrain) (position:Vector3<m>) (rowOffset:float32 ref) =

    let rowReal = -position.Z/terrain.cellSpacing.Value
    let tmpRow = floor(rowReal)
    rowOffset := rowReal - tmpRow   
    // to "cell" space
    terrain.vertexData.Value.Length - 2 - (tmpRow |> int)



let private RetrieveTerrainCellCol (terrain:Terrain) (position:Vector3<m>) (colOffset:float32 ref) =

    let colReal = position.X/terrain.cellSpacing.Value
    let tmpCol = floor(colReal) 
    colOffset := colReal - tmpCol
    // to "cell" space
    tmpCol |> int



let private RetrieveTerrainCell (terrain:Terrain) (position:Vector3<m>) (row:int ref) (col:int ref) (rowOffset:float32 ref) (colOffset:float32 ref) =

    row := RetrieveTerrainCellRow terrain position rowOffset
    col := RetrieveTerrainCellCol terrain position colOffset

    if !row >= 0 && !row < terrain.cellData.Value.Length then
        if !col >= 0 && !col < terrain.cellData.Value.[0].Length then
            Some(terrain.cellData.Value.[!row].[!col])
        else
            None
    else
        None



let internal IsTerrainCellInRange (terrain:Terrain) (position:Vector3<m>) (minCol:int) (maxCol:int) (col:int ref) =

    col := RetrieveTerrainCellCol terrain position (ref 0.0f)
    !col >= minCol && !col <= maxCol




let private RetrieveTerrainProjection (position:Vector3<m>) (terrainCell:TerrainCell) (rowOffset:float32) (colOffset:float32) =
    if terrainCell.type1 then
        if colOffset + rowOffset >= 1.0f then           // UPPER TRIANGLE
            let s = (terrainCell.upperTriangle.v1.Value.position - terrainCell.upperTriangle.v0.Value.position)
            let t = (terrainCell.upperTriangle.v2.Value.position - terrainCell.upperTriangle.v0.Value.position)
            terrainCell.upperTriangle.v0.Value.position + (s * (1.0f-colOffset)) + (t * (1.0f-rowOffset))
        else                                            // LOWER TRIANGLE
            let s = (terrainCell.lowerTriangle.v2.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            let t = (terrainCell.lowerTriangle.v1.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            terrainCell.lowerTriangle.v0.Value.position + (s * colOffset) + (t * rowOffset)
    else
        if (1.0f - colOffset) + rowOffset >= 1.0f then  // UPPER TRIANGLE
            let s = (terrainCell.upperTriangle.v1.Value.position - terrainCell.upperTriangle.v0.Value.position)
            let t = (terrainCell.upperTriangle.v2.Value.position - terrainCell.upperTriangle.v0.Value.position)
            terrainCell.upperTriangle.v0.Value.position + (s * colOffset) + (t * (1.0f-rowOffset))
        else                                            // LOWER TRIANGLE
            let s = (terrainCell.lowerTriangle.v2.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            let t = (terrainCell.lowerTriangle.v1.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            terrainCell.lowerTriangle.v0.Value.position + (s * (1.0f-colOffset)) + (t * rowOffset)



let private RetrieveTerrainProjectionWithPreciseNormal (position:Vector3<m>) (terrainCell:TerrainCell) (rowOffset:float32) (colOffset:float32) (normal:Vector3<1> ref) =

    if terrainCell.type1 then
        normal := Vector3.Lerp(rowOffset, Vector3.Lerp(colOffset, terrainCell.upperTriangle.v0.Value.normal, terrainCell.upperTriangle.v1.Value.normal), Vector3.Lerp(colOffset, terrainCell.upperTriangle.v2.Value.normal, terrainCell.lowerTriangle.v0.Value.normal))
        if colOffset + rowOffset >= 1.0f then           // UPPER TRIANGLE
            let s = (terrainCell.upperTriangle.v1.Value.position - terrainCell.upperTriangle.v0.Value.position)
            let t = (terrainCell.upperTriangle.v2.Value.position - terrainCell.upperTriangle.v0.Value.position)
            terrainCell.upperTriangle.v0.Value.position + (s * (1.0f-colOffset)) + (t * (1.0f-rowOffset))
        else                                            // LOWER TRIANGLE
            let s = (terrainCell.lowerTriangle.v2.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            let t = (terrainCell.lowerTriangle.v1.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            terrainCell.lowerTriangle.v0.Value.position + (s * colOffset) + (t * rowOffset)
    else
        normal := Vector3.Lerp(rowOffset, Vector3.Lerp(colOffset, terrainCell.upperTriangle.v1.Value.normal, terrainCell.upperTriangle.v0.Value.normal), Vector3.Lerp(colOffset, terrainCell.lowerTriangle.v0.Value.normal, terrainCell.upperTriangle.v2.Value.normal))
        if (1.0f - colOffset) + rowOffset >= 1.0f then  // UPPER TRIANGLE
            let s = (terrainCell.upperTriangle.v1.Value.position - terrainCell.upperTriangle.v0.Value.position)
            let t = (terrainCell.upperTriangle.v2.Value.position - terrainCell.upperTriangle.v0.Value.position)
            terrainCell.upperTriangle.v0.Value.position + (s * colOffset) + (t * (1.0f-rowOffset))
        else                                            // LOWER TRIANGLE
            let s = (terrainCell.lowerTriangle.v2.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            let t = (terrainCell.lowerTriangle.v1.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            terrainCell.lowerTriangle.v0.Value.position + (s * (1.0f-colOffset)) + (t * rowOffset)



let private RetrieveTerrainProjectionWithApproximateNormal (position:Vector3<m>) (terrainCell:TerrainCell) (rowOffset:float32) (colOffset:float32) (normal:Vector3<1> ref) =

    if terrainCell.type1 then
        if colOffset + rowOffset >= 1.0f then           // UPPER TRIANGLE
            normal := terrainCell.upperTriangle.normal
            let s = (terrainCell.upperTriangle.v1.Value.position - terrainCell.upperTriangle.v0.Value.position)
            let t = (terrainCell.upperTriangle.v2.Value.position - terrainCell.upperTriangle.v0.Value.position)
            terrainCell.upperTriangle.v0.Value.position + (s * (1.0f-colOffset)) + (t * (1.0f-rowOffset))
        else                                            // LOWER TRIANGLE
            normal := terrainCell.lowerTriangle.normal
            let s = (terrainCell.lowerTriangle.v2.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            let t = (terrainCell.lowerTriangle.v1.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            terrainCell.lowerTriangle.v0.Value.position + (s * colOffset) + (t * rowOffset)
    else
        if (1.0f - colOffset) + rowOffset >= 1.0f then  // UPPER TRIANGLE
            normal := terrainCell.upperTriangle.normal
            let s = (terrainCell.upperTriangle.v1.Value.position - terrainCell.upperTriangle.v0.Value.position)
            let t = (terrainCell.upperTriangle.v2.Value.position - terrainCell.upperTriangle.v0.Value.position)
            terrainCell.upperTriangle.v0.Value.position + (s * colOffset) + (t * (1.0f-rowOffset))
        else                                            // LOWER TRIANGLE
            normal := terrainCell.lowerTriangle.normal
            let s = (terrainCell.lowerTriangle.v2.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            let t = (terrainCell.lowerTriangle.v1.Value.position - terrainCell.lowerTriangle.v0.Value.position)
            terrainCell.lowerTriangle.v0.Value.position + (s * (1.0f-colOffset)) + (t * rowOffset)



let internal IsOverTerrain (terrain:Terrain) (position:Vector3<m>) =

    let rowOffset = ref 0.0f
    let colOffset = ref 0.0f
    let currentCell = RetrieveTerrainCell terrain position (ref 0) (ref 0) rowOffset colOffset

    match currentCell with
    | Some(currentCell) -> position.Y > (RetrieveTerrainProjection position currentCell !rowOffset !colOffset).Y
    | None -> false



let internal TerrainCollision (terrain:Terrain) (position:Vector3<m>) (terrainPosition:Vector3<m>ref) (normal:Vector3<1>ref) (terrainPatchIndex:int ref) =

    let rowOffset = ref 0.0f
    let colOffset = ref 0.0f
    let row = ref 0

    let currentCell = RetrieveTerrainCell terrain position row (ref 0) rowOffset colOffset

    match currentCell with
    | Some(currentCell) ->  terrainPosition := RetrieveTerrainProjectionWithPreciseNormal position currentCell !rowOffset !colOffset normal
                            terrainPatchIndex := floor((!row |> float32)/(terrain.cellData.Value.[0].Length |> float32)) |> int
                            1.0f - (!normal).Y > terrain.maxWalkableSlope
    | None -> true